#!/usr/bin/env python
# coding: utf-8

"""
Created on 04/11/2011

@author: Rubén López
"""

import wx.aui
import wx.lib.scrolledpanel as scrolled
import os.path
import time
from compiler import DefinitionToRegex, RegexToNFA, NFAtoDFA, MinimizeDFA, DFAtoBNF
from wx.lib.floatcanvas import NavCanvas
from vilean.bnf import BNF

BORDER = 5
FLAG1 = wx.TOP | wx.RIGHT | wx.LEFT
FLAG2 = wx.TOP | wx.RIGHT | wx.LEFT | wx.EXPAND
FLAG3 = wx.TOP | wx.RIGHT | wx.LEFT | wx.BOTTOM | wx.EXPAND
 
class InterpreterPage(scrolled.ScrolledPanel):
    """Main page that shows options to select a regular definition, enter an
    input string and test if it es accepted by the definition."""
    
    def __init__(self, parent, number):
        scrolled.ScrolledPanel.__init__(self, parent)
        self.number = number
        self.dirname = "."
        self.filename = ""
        
        # Select the regular definition
        bt0 = wx.Button(self, label='Select Regular Definition')
        self.Bind(wx.EVT_BUTTON, self.OnSelectDefinition, bt0)
        
        # Show the alphabet
        st0 = wx.StaticText(self, label='Alphabet: ')
        tc0 = wx.TextCtrl(self)
        tc0.SetEditable(False)
        
        # Show the regular definition
        st1 = wx.StaticText(self, label='Definition: ')
        tc1 = wx.TextCtrl(self, style=wx.TE_MULTILINE, size=(-1, 80))
        tc1.SetEditable(False)
        
        # Show the BNF
        st3 = wx.StaticText(self, label='BNF: ')
        tc3 = wx.TextCtrl(self, style=wx.TE_MULTILINE, size=(-1, 80))
        tc3.SetEditable(False)
        
        # Layout the Definitions and the BNF in horizontal
        vbox1 = wx.BoxSizer(wx.VERTICAL)
        vbox1.Add(st1, flag=FLAG2, border=BORDER)
        vbox1.Add(tc1, flag=FLAG2, border=BORDER)
        vbox2 = wx.BoxSizer(wx.VERTICAL)
        vbox2.Add(st3, flag=FLAG2, border=BORDER)
        vbox2.Add(tc3, flag=FLAG2, border=BORDER)
        hbx = wx.BoxSizer(wx.HORIZONTAL)
        hbx.Add(vbox1, 1)
        hbx.Add(vbox2, 1)
        
        # Allow user input
        st2 = wx.StaticText(self, label='Input: ')
        tc2 = wx.TextCtrl(self)
        
        # Let user test the input
        bt1 = wx.Button(self, label='Test Input')
        self.Bind(wx.EVT_BUTTON, self.OnTestInput, bt1)
        
        # Show the diagrams
        nb = wx.aui.AuiNotebook(self)
        self.def_page = DefinitionPage(nb)
        self.nfa_page = NFAPage(nb)
        self.dfa_page = DFAPage(nb)
        self.min_page = MinDFAPage(nb)
        nb.AddPage(self.def_page, "Definitions")
        nb.AddPage(self.nfa_page, "NFA")
        nb.AddPage(self.dfa_page, "DFA")
        nb.AddPage(self.min_page, "Minimum DFA")
        
        # Bind event to avoid closed pages
        self.Bind(wx.aui.EVT_AUINOTEBOOK_PAGE_CLOSE, 
                  self.OnAutomataPageClose, nb)
        
        # Layout everythin in vertical
        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(bt0, flag=FLAG1, border=BORDER)
        vbox.Add(st0, flag=FLAG1, border=BORDER)
        vbox.Add(tc0, flag=FLAG2, border=BORDER)
#        vbox.Add(st1, flag=FLAG1, border=BORDER)
#        vbox.Add(tc1, flag=FLAG2, border=BORDER)
        vbox.Add(hbx, flag=wx.EXPAND)
        vbox.Add(st2, flag=FLAG1, border=BORDER)
        vbox.Add(tc2, flag=FLAG3, border=BORDER)
        vbox.Add(bt1, flag=FLAG1, border=BORDER)
        vbox.Add((-1, 10))
        vbox.Add(nb, 1, wx.EXPAND)
        self.SetSizer(vbox)

        # Hold references to text controls
        self.alphabet = tc0
        self.definition = tc1
        self.input = tc2
        self.bnf = tc3
        
        # References to automatas
        self.nfa = None
        self.dfa = None
        self.min = None

        # Last...
        self.SetupScrolling()
        
    def deleteImages(self, all=False):
        """Delete all temporary images for this page."""
        
        self.nfa_page.deleteImages()
        self.dfa_page.deleteImages()
        self.min_page.deleteImages()
        
        if all:
            self.def_page.deleteImages()
            if self.filename != "":
                os.remove('png/' + self.filename + '.nfa_page.png')
                os.remove('png/' + self.filename + '.dfa_page.png')
                os.remove('png/' + self.filename + '.min_page.png')
        
    def OnAutomataPageClose(self, event):
        """Do nothing when trying to close this page."""
        
        event.Veto()
        
    def OnSelectDefinition(self, event):
        """Open a dialog to let the user select a regular definition. When
        a file is selected, the NFA, DFA and Minimum DFA pages are updated
        whit the corresponding new diagrams."""
        
        if self.askUserForFilename(set_title=False, style=wx.OPEN,
                                   message="Choose a file",
                                   defaultDir=self.dirname, wildcard="*.def"):
            
            textfile = open(os.path.join(self.dirname, self.filename), 'r')
            text = textfile.read()
            
            # Get the alphabet and the definition from file
            alphabet, sep, definition = text.partition(";;")
            id, sep, alph = alphabet.partition(":=")
            id, sep, defi = definition.partition(":=")
            
            # Bind to text controls
            self.alphabet.SetValue(alph)
            self.definition.SetValue(defi)
            
            # Close file
            textfile.close()
            
            # Convert alphabet to an array
            alphabet = []
            for char in alph:
                alphabet.append(char)
                
            # Convert regular definition to nfa
            d = DefinitionToRegex(defi, alphabet)
            exp = d.traslate()
            
            # Draw definitions and save data (name, filename)
            images = d.draw(self.filename + '.def_page')
            
            t = RegexToNFA(exp, alphabet)
            nfa = t.traslate()
            nfa.draw(self.filename + '.nfa_page')
            
            # Convert nfa to dfa
            n2d = NFAtoDFA()
            dfa = n2d.convert(nfa)
            dfa.draw(self.filename + '.dfa_page')
            
            # Convert nfa to minimum dfa
            min = MinimizeDFA()
            dfa2 = min.remove_dead(dfa)
            dfa2 = min.remove_inac(dfa2)
            dfa2 = min.minimize(dfa2)
            dfa2.draw(self.filename + '.min_page')
            
            # Convert minimun dfa to BNF
            d2b = DFAtoBNF()
            bnf = d2b.convert(dfa2)
            
            # Load data for def_page
            for item in images:
                self.def_page.definitions.Append(item[0], item[1])
            self.def_page.images = images
            self.def_page.definitions.SetSelection(0)
            self.def_page.addImage(images[0][1])
                        
            # Load data for nfa_page
            filename = self.filename + '.nfa_page'
            self.nfa_page.addImage(filename)
            self.nfa_page.transitions.SetValue(nfa.__str__())
            self.nfa_page.SetupScrolling()
            self.nfa_page.Refresh()
            
            # Load data for dfa_page
            filename = self.filename + '.dfa_page'
            self.dfa_page.addImage(filename)
            self.dfa_page.transitions.SetValue(dfa.__str__())
            self.dfa_page.SetupScrolling()
            self.dfa_page.Refresh()
            
            # Load data for minimum dfa_page
            filename = self.filename + '.min_page'
            self.min_page.addImage(filename)
            self.min_page.transitions.SetValue(dfa2.__str__())
            self.min_page.SetupScrolling()
            self.min_page.Refresh()
            
            # Load data for bnf
            self.bnf.SetValue(bnf.__str__())
            
            # Save references to automatas
            self.nfa = nfa
            self.dfa = dfa
            self.min = dfa2
            
            self.SetupScrolling()
            
    def OnTestInput(self, event):
        """Test if the input string is accepted by the selected regular
        definition, updates the derivations table for the DFA an the 
        Minimun DFA and generates images for each step of the derivation."""
        
        # Delete previous input images.
        self.deleteImages()
        
        # Check if a definition was selected.
        if self.dfa is None:
            dlg = wx.MessageDialog(self, 'Select a regular definition',
                                   'Error',
                                   wx.OK | wx.ICON_WARNING)
            dlg.ShowModal()
            dlg.Destroy()
            return False
        
        # Check if there is input
        input = self.input.GetValue()
        if input == "":
            dlg = wx.MessageDialog(self, 'Enter some input',
                                   'Error',
                                   wx.OK | wx.ICON_WARNING)
            dlg.ShowModal()
            dlg.Destroy()
            return False
        
        # Directory for temporary images
        tmp = "tmp/" + str(self.number) + "."

        # Results for nfa
        result, derivations, images = self.nfa.accepts(input, tmp + "nfa")
        self.nfa_page.derivations.Clear()
        for item in derivations:
            self.nfa_page.derivations.Append(item[0], item[1])
        self.nfa_page.images = images
        
        # Results for dfa
        result, derivations, images = self.dfa.accepts(input, tmp + "dfa")
        self.dfa_page.derivations.Clear()
        for item in derivations:
            self.dfa_page.derivations.Append(item[0], item[1])
        self.dfa_page.images = images

        # Results for minimum dfa
        result, derivations, images = self.min.accepts(input, tmp + "min")
        self.min_page.derivations.Clear()
        for item in derivations:
            self.min_page.derivations.Append(item[0], item[1])
        self.min_page.images = images
        
        # Show result dialog
        if result:
            dlg = wx.MessageDialog(self, 'Input accepted', 'Ok :)',
                                       wx.OK | wx.ICON_INFORMATION)
            dlg.ShowModal()
            dlg.Destroy()
        else:
            dlg = wx.MessageDialog(self, 'Input not accepted', 'Ok :(',
                                       wx.OK | wx.ICON_ERROR)
            dlg.ShowModal()
            dlg.Destroy()
            
    def askUserForFilename(self, set_title=True, **dialogOptions):
        """Open the file dialog to let the user set a file name."""
        
        dialog = wx.FileDialog(self, **dialogOptions)
        if dialog.ShowModal() == wx.ID_OK:
            userProvidedFilename = True
            self.filename = dialog.GetFilename()
            self.dirname = dialog.GetDirectory()
            if set_title:
                self.SetTitle() # Update window title with new filename
        else:
            userProvidedFilename = False
        dialog.Destroy()
        return userProvidedFilename
    
    def SetTitle(self):
        """Set the title of this window to the name of the file
        being edited."""
        
        #super(EditorFrame, self).SetTitle("Editor: %s" % self.filename)
        # TODO: set page title to .def filename

class AutomataPage(scrolled.ScrolledPanel):
    """Superclass for NFA, DFA and Minimum DFA pages. Show transitions, 
    derivations, a button to play the animation and resizable panel for
    the images."""
    
    def __init__(self, parent):
        scrolled.ScrolledPanel.__init__(self, parent)
        
        # Show transition table
        st1 = wx.StaticText(self, label="Transitions:")
        tc1 = wx.TextCtrl(self, style=wx.TE_MULTILINE, size=(120, -1))
        tc1.SetEditable(False)
        
        # Show derivations
        st2 = wx.StaticText(self, label="Derivations:")
        lb1 = wx.ListBox(self, size=(120, -1), choices=[], style=wx.LB_SINGLE)
        self.Bind(wx.EVT_LISTBOX, self.OnDerivatiosClick, lb1)
        
        # Play animation
        bt0 = wx.Button(self, label='Play')
        self.Bind(wx.EVT_BUTTON, self.OnPlayClick, bt0)
        
        # Show image
        panel = NavCanvas.NavCanvas(self, BackgroundColor="WHITE")

        # Layout the text controls in vertical
        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(st1, flag=FLAG1, border=BORDER)
        vbox.Add(tc1, 1, flag=FLAG3, border=BORDER)
        vbox.Add(st2, flag=FLAG1, border=BORDER)
        vbox.Add(lb1, 1, flag=FLAG3, border=BORDER)
        vbox.Add(bt0, flag=FLAG3, border=BORDER)
        
        # Layout the text controls and the image in horizontal
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add(vbox, flag=wx.EXPAND)
        hbox.Add(panel, 1, flag=wx.EXPAND)
        
        # Hold a reference to image, transitions table and derivations
        self.canvas = panel.Canvas
        self.transitions = tc1
        self.derivations = lb1
        self.images = {}
        
        # Finaly...
        self.SetSizer(hbox)
        self.SetupScrolling()
        
    def addImage(self, filename):
        """Load image from filename into the image panel."""
        
        self.canvas.InitAll()
        filename = "png/" + filename + ".png"
        img = wx.Image(filename, wx.BITMAP_TYPE_PNG)
        self.canvas.AddScaledBitmap(wx.BitmapFromImage(img), (0, 0),
                                    Height=500, Position="tl")
        self.canvas.ZoomToBB()
        
    def deleteImages(self):
        """Delete all temporary images for this automata."""
        
        for filename in self.images.values():
            os.remove("png/" + filename + ".png")
        
    def changeDerivation(self, state):
        """Load image corresponding to sate into the image panel."""
        
        filename = self.images[state]
        self.addImage(filename)

    def OnDerivatiosClick(self, event):
        """Load image for the selected derivation into the image panel."""
        
        state = event.GetClientData()
        self.changeDerivation(state)
        
    def OnPlayClick(self, event):
        """Plays an animation of the derivation proces."""
        
        for i in range(self.derivations.GetCount()):
            time.sleep(0.5)
            self.derivations.SetSelection(i)
            state = self.derivations.GetClientData(i)
            self.changeDerivation(state)
        
class NFAPage(AutomataPage):
    
    def __init__(self, parent):
        AutomataPage.__init__(self, parent)

class DFAPage(AutomataPage):
    
    def __init__(self, parent):
        AutomataPage.__init__(self, parent)

class MinDFAPage(AutomataPage):
    
    def __init__(self, parent):
        AutomataPage.__init__(self, parent)
        
class DefinitionPage(scrolled.ScrolledPanel):
    """Page that shows a list box with the names of the definitions and an 
    image of the selected definition."""
    
    def __init__(self, parent):
        scrolled.ScrolledPanel.__init__(self, parent)
        
        # Show definitions
        st2 = wx.StaticText(self, label="Definitions:")
        lb1 = wx.ListBox(self, size=(120, -1), choices=[], style=wx.LB_SINGLE)
        self.Bind(wx.EVT_LISTBOX, self.OnDefinitionClick, lb1)
        
        # Show image
        panel = NavCanvas.NavCanvas(self, BackgroundColor="WHITE")

        # Layout the text controls in vertical
        vbox = wx.BoxSizer(wx.VERTICAL)
        vbox.Add(st2, flag=FLAG1, border=BORDER)
        vbox.Add(lb1, 1, flag=FLAG3, border=BORDER)
        
        # Layout the text controls and the image in horizontal
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox.Add(vbox, flag=wx.EXPAND)
        hbox.Add(panel, 1, flag=wx.EXPAND)
        
        # Hold a reference to image, transitions table and derivations
        self.canvas = panel.Canvas
        self.definitions = lb1
        self.images = []
        
        # Finaly...
        self.SetSizer(hbox)
        self.SetupScrolling()
        
    def addImage(self, filename):
        """Load image from filename into the image panel."""
        
        self.canvas.InitAll()
        filename = "png/" + filename + ".png"
        img = wx.Image(filename, wx.BITMAP_TYPE_PNG)
        self.canvas.AddScaledBitmap(wx.BitmapFromImage(img), (0, 0),
                                    Height=500, Position="tl")
        self.canvas.ZoomToBB()
        
    def deleteImages(self):
        """Delete all temporary images."""
        
        for item in self.images:
            filename = item[1]
            os.remove("png/" + filename + ".png")

    def OnDefinitionClick(self, event):
        """Load image for the selected definition into the image panel."""
        
        self.addImage(event.GetClientData())